package com.bigfans.framework.dao;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Id;

import com.bigfans.framework.cache.CacheEvict;
import com.bigfans.framework.cache.CacheList;
import com.bigfans.framework.cache.Cacheable;
import com.bigfans.framework.cache.ModelCacheKeyGenerator;
import com.bigfans.framework.exception.DBRuntimeException;
import com.bigfans.framework.model.AbstractModel;
import com.bigfans.framework.utils.CollectionUtils;
import com.bigfans.framework.utils.ReflectionUtils;
import com.bigfans.framework.utils.StringHelper;

/**
 * @author lichong
 * @version V1.0
 * @Title:
 * @Description:
 * @date 2016年1月13日 上午8:37:40
 */
public class WrappedJdbcDAO {

    private static final String AND = " AND ";
    private static final String WRAPPER = "`";
    private JdbcDAO jdbcDAO;

    private WrappedJdbcDAO(JdbcDAO jdbcDAO) {
        this.jdbcDAO = jdbcDAO;
    }

    private static class InstanceHolder {
        static WrappedJdbcDAO instance = new WrappedJdbcDAO(new SimpleJdbcDAO());
    }

    public static WrappedJdbcDAO getInstance() {
        return InstanceHolder.instance;
    }

    public <M extends AbstractModel> Number insert(M model) {
        Map<String, Object> valuesMap = this.parseNotNulValues(model);
        valuesMap.put(AbstractModel.COLUMN_CREATE_DATE, new Date());
        valuesMap.put(AbstractModel.COLUMN_UPDATE_DATE, new Date());
        StringBuilder SQL = new StringBuilder(30);
        StringBuilder columnClause = new StringBuilder(20);
        StringBuilder valueClause = new StringBuilder(20);
        int index = 1;
        int size = valuesMap.size();
        List<Object> params = new ArrayList<Object>();
        for (Map.Entry<String, Object> entry : valuesMap.entrySet()) {
            columnClause.append(WRAPPER);
            columnClause.append(entry.getKey());
            columnClause.append(WRAPPER);
            params.add(entry.getValue());
            valueClause.append("?");
            if (index != size) {
                columnClause.append(",");
                valueClause.append(",");
            }
            index++;
        }
        SQL.append("INSERT INTO `").append(model.getTableName()).append(WRAPPER);
        SQL.append(" (").append(columnClause).append(") ");
        SQL.append(" VALUES ");
        SQL.append(" (").append(valueClause).append(") ");
        SQL.trimToSize();
        int c = 0;
        if (StringHelper.isEmpty(model.getId())) {
            GeneratedKey generatedKey = new GeneratedKey();
            c = jdbcDAO.insert(SQL.toString(), params.toArray(), generatedKey);
            model.setId(generatedKey.getKey().toString());
        } else {
            c = jdbcDAO.insert(SQL.toString(), params.toArray());
        }

        return c;
    }

    /**
     * TODO
     *
     * @param modelList
     * @return
     */
    public <M extends AbstractModel> Number batchInsert(List<M> modelList) {
        if (CollectionUtils.isEmpty(modelList)) {
            throw new DBRuntimeException("No record was found when inserting.");
        }
        List<Map<String, Object>> valuesList = new ArrayList<Map<String, Object>>();
        StringBuilder SQL = new StringBuilder(30);
        StringBuilder columnClause = new StringBuilder(20);
        StringBuilder valueClause = new StringBuilder(20);
        SQL.append("INSERT INTO `");
        SQL.append(modelList.get(0).getTableName());
        SQL.append(WRAPPER);
        int index = 1;
        List<Object> params = new ArrayList<Object>();
        for (M model : modelList) {
            Map<String, Object> valuesMap = this.parseNotNulValues(model);
            valuesMap.put(AbstractModel.COLUMN_CREATE_DATE, new Date());
            valuesMap.put(AbstractModel.COLUMN_UPDATE_DATE, new Date());
            valuesList.add(valuesMap);
            valueClause.append(" ( ");
            for (Map.Entry<String, Object> entry : valuesMap.entrySet()) {
                if (index == 1) {
                    columnClause.append(WRAPPER);
                    columnClause.append(entry.getKey());
                    columnClause.append(WRAPPER);
                    columnClause.append(",");
                }
                params.add(entry.getValue());
                valueClause.append("?");
                valueClause.append(",");
            }
            valueClause.deleteCharAt(valueClause.lastIndexOf(","));
            valueClause.append(" ), ");
            index++;
        }
        columnClause.deleteCharAt(columnClause.lastIndexOf(","));
        valueClause.deleteCharAt(valueClause.lastIndexOf(","));
        SQL.append(" ( ");
        SQL.append(columnClause);
        SQL.append(" ) ");
        SQL.append(" VALUES ");
        SQL.append(valueClause);
        jdbcDAO.batchInsert(SQL.toString(), params.toArray());
        return 0;
    }

    @CacheEvict(keyGenerator = ModelCacheKeyGenerator.class)
    public <M extends AbstractModel> Number delete(M model) {
        Map<String, Object> valuesMap = this.parseUpdateValues(model);
        StringBuilder SQL = new StringBuilder(30);
        StringBuilder whereClause = new StringBuilder(20);
        int index = 1;
        int size = valuesMap.size();
        List<Object> params = new ArrayList<Object>();
        for (Map.Entry<String, Object> entry : valuesMap.entrySet()) {
            whereClause.append(entry.getKey()).append("=?");
            params.add(entry.getValue());
            if (index != size) {
                whereClause.append(AND);
            }
            index++;
        }
        SQL.append("DELETE FROM ").append(model.getTableName());
        SQL.append(" WHERE ");
        SQL.append(whereClause);
        SQL.trimToSize();
        return jdbcDAO.delete(SQL.toString(), params.toArray());
    }

    @CacheEvict(keyGenerator = ModelCacheKeyGenerator.class)
    public <M extends AbstractModel> Number update(M model) {
        if (model.getId() == null || "".equals(model.getId())) {
            throw new DBRuntimeException("ID field was not specified when updating record");
        }
        Map<String, Object> valuesMap = this.parseUpdateValues(model);
        StringBuilder SQL = new StringBuilder(30);
        StringBuilder setClause = new StringBuilder(20);
        int index = 1;
        int size = valuesMap.size();
        List<Object> params = new ArrayList<Object>();
        for (Map.Entry<String, Object> entry : valuesMap.entrySet()) {
            setClause.append(WRAPPER);
            setClause.append(entry.getKey());
            setClause.append(WRAPPER);
            setClause.append("=?");
            params.add(entry.getValue());
            if (index != size) {
                setClause.append(",");
            }
            index++;
        }
        SQL.append("UPDATE ");
        SQL.append(WRAPPER);
        SQL.append(model.getTableName());
        SQL.append(WRAPPER);
        SQL.append(" SET ");
        SQL.append(setClause);
        SQL.append(" WHERE ID=? AND DELETED=? ");
        params.add(model.getId());
        params.add(model.getDeleted());
        return jdbcDAO.update(SQL.toString(), params.toArray());
    }

    @Cacheable(keyGenerator = ModelCacheKeyGenerator.class)
    public <M extends AbstractModel, V extends AbstractModel> V load(M model) {
        // 拼接要查询的列
        StringBuilder columns = new StringBuilder(20);
        Map<String, Field> columnMap = model.getDBFieldMap();
        for (Map.Entry<String, Field> entry : columnMap.entrySet()) {
            columns.append(entry.getKey()).append(" AS ").append(entry.getValue().getName())
                    .append(",");
        }
        columns.trimToSize();
        columns.deleteCharAt(columns.capacity() - 1);
        // 拼接where条件和参数
        StringBuilder where = new StringBuilder(20);
        List<Object> params = new ArrayList<Object>();
        if (model.getId() != null && !"".equals(model.getId())) {
            where.append("id=? AND deleted=? ");
            params.add(model.getId());
            params.add(model.getDeleted());
        } else {
            Map<String, Object> valuesMap = this.parseNotNulValues(model);
            int index = 1;
            int size = valuesMap.size();
            for (Map.Entry<String, Object> entry : valuesMap.entrySet()) {
                where.append(entry.getKey()).append("=?");
                params.add(entry.getValue());
                if (index != size) {
                    where.append(AND);
                }
                index++;
            }
        }
        StringBuilder SQL = new StringBuilder(50);
        SQL.append("SELECT ");
        SQL.append(columns);
        SQL.append(" FROM ");
        SQL.append(model.getTableName());
        SQL.append(" WHERE ");
        SQL.append(where);
        return (V) jdbcDAO.load(model.getClass(), SQL.toString(), params.toArray());
    }

    @CacheList(keyGenerator = ModelCacheKeyGenerator.class)
    public <M extends AbstractModel> List<M> list(M model, Long start, Long pagesize) {
        // 拼接要查询的列
        StringBuilder columns = new StringBuilder(20);
        Map<String, Field> columnMap = model.getDBFieldMap();
        for (Map.Entry<String, Field> entry : columnMap.entrySet()) {
            columns.append(entry.getKey()).append(" AS ").append(entry.getValue().getName())
                    .append(",");
        }
        columns.trimToSize();
        columns.deleteCharAt(columns.capacity() - 1);
        // 拼接where条件和参数
        StringBuilder where = new StringBuilder(20);
        List<Object> params = new ArrayList<Object>();
        Map<String, Object> valuesMap = this.parseNotNulValues(model);
        int index = 1;
        int size = valuesMap.size();
        for (Map.Entry<String, Object> entry : valuesMap.entrySet()) {
            where.append(entry.getKey()).append("=?");
            params.add(entry.getValue());
            if (index != size) {
                where.append(AND);
            }
            index++;
        }
        StringBuilder SQL = new StringBuilder(50);
        SQL.append("SELECT ");
        SQL.append(columns);
        SQL.append(" FROM ");
        SQL.append(model.getTableName());
        SQL.append(" WHERE ");
        SQL.append(where);
        if (start != null && pagesize != null) {
            SQL.append(" LIMIT ");
            SQL.append(start);
            SQL.append(",");
            SQL.append(pagesize);
        }
        return (List<M>) jdbcDAO.list(model.getClass(), SQL.toString(), params.toArray());
    }

    public <M extends AbstractModel> Number count(M model) {
        Map<String, Object> valuesMap = this.parseNotNulValues(model);
        StringBuilder SQL = new StringBuilder(30);
        StringBuilder whereClause = new StringBuilder(20);
        int index = 1;
        int size = valuesMap.size();
        List<Object> params = new ArrayList<Object>();
        for (Map.Entry<String, Object> entry : valuesMap.entrySet()) {
            whereClause.append(entry.getKey()).append("=?");
            params.add(entry.getValue());
            if (index != size) {
                whereClause.append(AND);
            }
            index++;
        }
        SQL.append("SELECT COUNT(1) FROM ").append(model.getTableName());
        SQL.append(" WHERE ");
        SQL.append(whereClause);
        SQL.trimToSize();
        return jdbcDAO.count(SQL.toString(), params.toArray());
    }

    private Map<String, Object> parseNotNulValues(AbstractModel model) {
        Map<String, Object> valuesMap = new HashMap<String, Object>();
        Map<String, Field> columnMap = model.getDBFieldMap();
        for (Map.Entry<String, Field> entry : columnMap.entrySet()) {
            // 调用field的get方法获取属性值
            Object value = ReflectionUtils.getProperty(model, entry.getValue().getName());
            if (value != null) {
                valuesMap.put(entry.getKey(), value);
            }
        }
        if (valuesMap.isEmpty()) {
            throw new DBRuntimeException("no field was set when inserting record!");
        }
        return valuesMap;
    }

    private Map<String, Object> parseUpdateValues(AbstractModel model) {
        Map<String, Object> valuesMap = new HashMap<String, Object>();
        Map<String, Field> columnMap = model.getDBFieldMap();
        for (Map.Entry<String, Field> entry : columnMap.entrySet()) {
            Field field = entry.getValue();
            // 调用field的get方法获取属性值
            Object value = ReflectionUtils.getProperty(model, field.getName());
            if (value == null) {
                continue;
            }
            if (field.getAnnotation(Id.class) == null && !field.getAnnotation(Column.class).updatable()) {
                continue;
            }
            valuesMap.put(entry.getKey(), value);
        }
        if (valuesMap.isEmpty()) {
            throw new DBRuntimeException("no field was set when deleting record!");
        }
        return valuesMap;
    }
}
